package tc.de.krawallmann.gravoblocks.game.model;

import java.util.List;

/**
 * The Class TetrisGrid.
 * 
 * @author KrawallMann
 * @version 1.0
 */
public class TetrisGrid extends Grid<TetrisBlock> {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -238358412423728141L;
	/** The current tile. */
	private TetrisTile currentTile;

	/**
	 * Instantiates a new tetris grid.
	 * 
	 * @deprecated For deserialization only!
	 */
	@Deprecated
	public TetrisGrid() {
		super();
	}

	/**
	 * Instantiates a new tetris grid.
	 * 
	 * @param width
	 *            the width
	 * @param height
	 *            the height
	 */
	public TetrisGrid(final int width, final int height) {
		super(width, height);
		init();
	}

	public boolean[][] asBooleanArray() {
		final boolean[][] field = new boolean[getWidth() + 2][getHeight()];
		for (int y = 0; y < getHeight(); y++) {
			field[0][y] = true;
			for (int x = 0; x < getWidth(); x++) {
				field[x + 1][y] = getBlockAt(x, y).isBlocked() || getBlockAt(x, y).isMetallic();
			}
			field[getWidth() + 1][y] = true;
		}
		return field;
	}

	/**
	 * Can move.
	 * 
	 * @param tile
	 *            the tile
	 * @param deltaX
	 *            the delta x
	 * @param deltaY
	 *            the delta y
	 * @return true, if successful
	 */
	public boolean canMove(final TetrisTile tile, final int deltaX, final int deltaY) {
		final Point[] relativePositions = tile.getCurrentRelativePositions();
		setPositions(tile.getXPosition() + deltaX, tile.getYPosition() + deltaY, relativePositions,
				false);
		final boolean ret =
				isFree(tile.getXPosition() + deltaX, tile.getYPosition() + deltaY,
						tile.getCurrentRelativePositions());
		if (ret) {
			setPositions(tile.getXPosition() + deltaX, tile.getYPosition() + deltaY,
					tile.getCurrentRelativePositions(), tile.getColor());
		} else {
			setPositions(tile.getXPosition() + deltaX, tile.getYPosition() + deltaY,
					relativePositions, true);
		}
		return ret;
	}

	/**
	 * Can rotate.
	 * 
	 * @param tile
	 *            the tile
	 * @param times
	 *            the times
	 * @return true, if successful
	 */
	public boolean canRotate(final TetrisTile tile, final int times) {
		final Point[] relativePositions = tile.getCurrentRelativePositions();
		setPositions(tile.getXPosition(), tile.getYPosition(), relativePositions, false);
		final boolean ret =
				isFree(tile.getXPosition(), tile.getYPosition(),
						tile.getRelativePositions(times + tile.getRotation()));
		if (ret) {
			setPositions(tile.getXPosition(), tile.getYPosition(),
					tile.getRelativePositions(tile.getRotation() + times), tile.getColor());
		} else {
			setPositions(tile.getXPosition(), tile.getYPosition(), relativePositions, true);
		}
		return ret;
	}

	/**
	 * Check bounds.
	 * 
	 * @param xPosition
	 *            the x position
	 * @param yPosition
	 *            the y position
	 * @return true, if successful
	 */
	public boolean checkBounds(final int xPosition, final int yPosition) {
		return (xPosition < getWidth()) && (yPosition < getHeight()) && (xPosition >= 0)
				&& (yPosition >= 0);
	}

	/**
	 * Gets the current tile.
	 * 
	 * @return the current tile
	 */
	public TetrisTile getCurrentTile() {
		return currentTile;
	}

	/**
	 * Initializes the.
	 */
	public final void init() {
		clear();
		for (int x = 0; x < getWidth(); x++) {
			for (int y = 0; y < getHeight(); y++) {
				getRow(y).add(new TetrisBlock(x, y, BlockColor.WHITE));
			}
		}
	}

	/**
	 * Insert.
	 * 
	 * @param tile
	 *            the tile
	 * @param xPosition
	 *            the x position
	 * @param yPosition
	 *            the y position
	 * @return true, if successful
	 */
	public boolean insert(final TetrisTile tile, final int xPosition, final int yPosition) {
		tile.setXPosition(xPosition);
		tile.setYPosition(yPosition);
		if (!isFree(tile)) {
			return false;
		}
		setPositions(xPosition, yPosition, tile.getCurrentRelativePositions(), tile.getColor());
		return true;
	}

	/**
	 * Checks if is free.
	 * 
	 * @param xPosition
	 *            the x position
	 * @param yPosition
	 *            the y position
	 * @param relativePositions
	 *            the relative positions
	 * @return true, if is free
	 */
	public boolean isFree(final int xPosition, final int yPosition, final Point[] relativePositions) {
		for (int i = 0; (i < relativePositions.length); i++) {
			if (checkBounds(xPosition + relativePositions[i].getXPosition(), yPosition
					+ relativePositions[i].getYPosition())) {
				final TetrisBlock block =
						getBlockAt(xPosition + relativePositions[i].getXPosition(), yPosition
								+ relativePositions[i].getYPosition());
				if (block.isBlocked() || (block.isTransparent() && block.isGhost())) {
					return false;
				}
			} else {
				return false;
			}
		}
		return true;
	}

	/**
	 * Move.
	 * 
	 * @param tile
	 *            the tile
	 * @param deltaX
	 *            the delta x
	 * @param deltaY
	 *            the delta y
	 * @return true, if successful
	 */
	public boolean move(final TetrisTile tile, final int deltaX, final int deltaY) {
		final Point[] relativePositions = tile.getCurrentRelativePositions();
		setPositions(tile.getXPosition(), tile.getYPosition(), relativePositions, false);
		final boolean ret =
				isFree(tile.getXPosition() + deltaX, tile.getYPosition() + deltaY,
						tile.getCurrentRelativePositions());
		if (ret) {
			this.setPositions(tile.getXPosition() + deltaX, tile.getYPosition() + deltaY,
					relativePositions, tile.getColor());
			tile.setXPosition(tile.getXPosition() + deltaX);
			tile.setYPosition(tile.getYPosition() + deltaY);
		} else {
			setPositions(tile.getXPosition(), tile.getYPosition(), relativePositions, true);
		}
		return ret;
	}

	/**
	 * Move ghost.
	 * 
	 * @param tile
	 *            the tile
	 * @param deltaX
	 *            the delta x
	 * @param deltyY
	 *            the delty y
	 * @return true, if successful
	 */
	public boolean moveGhost(final TetrisTile tile, final int deltaX, final int deltyY) {
		final Point[] relativePositions = tile.getCurrentRelativePositions();
		setGhost(tile, false);
		if (!isGhostFree(tile.getXPosition() + deltaX, tile.getYPosition() + deltyY,
				tile.getCurrentRelativePositions())) {
			return false;
		}
		for (final Point po : relativePositions) {
			getBlockAt(tile.getXPosition() + po.getXPosition() + deltaX,
					tile.getYPosition() + po.getYPosition() + deltyY).setGhost(true);
			getBlockAt(tile.getXPosition() + po.getXPosition() + deltaX,
					tile.getYPosition() + po.getYPosition() + deltyY).setColor(tile.getColor());
		}
		tile.setXPosition(tile.getXPosition() + deltaX);
		tile.setYPosition(tile.getYPosition() + deltyY);
		return true;
	}

	/**
	 * Removes the ghosts.
	 */
	public void removeGhosts() {
		for (final List<TetrisBlock> list : getBlocks()) {
			for (final TetrisBlock b : list) {
				if (b.isGhost()) {
					b.setGhost(false);
				}
			}
		}
	}

	/**
	 * Removes the tile.
	 * 
	 * @param tile
	 *            the tile
	 */
	public void removeTile(final TetrisTile tile) {
		setPositions(tile.getXPosition(), tile.getYPosition(), tile.getCurrentRelativePositions(),
				false);
	}

	/**
	 * Resets this grid.
	 */
	public void reset() {
		for (int x = 0; x < getWidth(); x++) {
			for (int y = 0; y < getHeight(); y++) {
				final TetrisBlock block = getBlockAt(x, y);
				block.setColor(BlockColor.WHITE);
				block.setGhost(false);
				block.setTransparent(false);
				block.setBlocked(false);
				block.setXPosition(x);
				block.setYPosition(y);
				block.setMetallic(false);
			}
		}
	}

	/**
	 * Rotate.
	 * 
	 * @param tile
	 *            the tile
	 * @param times
	 *            the times
	 */
	public void rotate(final TetrisTile tile, final int times) {
		final Point[] relativePositions = tile.getCurrentRelativePositions();
		setPositions(tile.getXPosition(), tile.getYPosition(), relativePositions, false);
		final boolean ret =
				isFree(tile.getXPosition(), tile.getYPosition(),
						tile.getRelativePositions(tile.getRotation() + times));
		if (ret) {
			setPositions(tile.getXPosition(), tile.getYPosition(),
					tile.getRelativePositions(tile.getRotation() + times), tile.getColor());
			tile.setRotation(tile.getRotation() + times);
		} else {
			setPositions(tile.getXPosition(), tile.getYPosition(), relativePositions, true);
		}
	}

	/**
	 * Sets the blocked.
	 * 
	 * @param tile
	 *            the tile
	 * @param isBlocked
	 *            the is blocked
	 */
	public void setBlocked(final TetrisTile tile, final boolean isBlocked) {
		for (final Point point : tile.getCurrentRelativePositions()) {
			getBlockAt(tile.getXPosition() + point.getXPosition(),
					tile.getYPosition() + point.getYPosition()).setBlocked(isBlocked);
		}
	}

	/**
	 * Sets the current tile.
	 * 
	 * @param currentTile
	 *            the new current tile
	 */
	public void setCurrentTile(final TetrisTile currentTile) {
		this.currentTile = currentTile;
	}

	/**
	 * Sets the ghost.
	 * 
	 * @param tile
	 *            the tile
	 * @param isGhost
	 *            the is ghost
	 */
	public void setGhost(final TetrisTile tile, final boolean isGhost) {
		for (final Point point : tile.getCurrentRelativePositions()) {
			getBlockAt(tile.getXPosition() + point.getXPosition(),
					tile.getYPosition() + point.getYPosition()).setGhost(isGhost);
		}
	}

	/**
	 * Sets the positions.
	 * 
	 * @param xPosition
	 *            the x position
	 * @param yPosition
	 *            the y position
	 * @param relativePositions
	 *            the relative positions
	 * @param color
	 *            the color
	 */
	public void setPositions(final int xPosition, final int yPosition,
			final Point[] relativePositions, final BlockColor color) {
		for (final Point point : relativePositions) {
			getBlockAt(xPosition + point.getXPosition(), yPosition + point.getYPosition())
					.setBlocked(true);
			getBlockAt(xPosition + point.getXPosition(), yPosition + point.getYPosition())
					.setColor(color);
		}
	}

	/**
	 * Sets the positions.
	 * 
	 * @param xPosition
	 *            the x position
	 * @param yPosition
	 *            the y position
	 * @param relativePositions
	 *            the relative positions
	 * @param value
	 *            the value
	 */
	public void setPositions(final int xPosition, final int yPosition,
			final Point[] relativePositions, final boolean value) {
		for (final Point point : relativePositions) {
			getBlockAt(xPosition + point.getXPosition(), yPosition + point.getYPosition())
					.setBlocked(value);
		}
	}

	/**
	 * Sets the transparent.
	 * 
	 * @param tile
	 *            the tile
	 * @param isTransparent
	 *            the is transparent
	 */
	public void setTransparent(final TetrisTile tile, final boolean isTransparent) {
		for (final Point point : tile.getCurrentRelativePositions()) {
			getBlockAt(tile.getXPosition() + point.getXPosition(),
					tile.getYPosition() + point.getYPosition()).setTransparent(isTransparent);
		}
	}

	@Override
	public String toString() {
		final StringBuilder sbuilder = new StringBuilder();
		sbuilder.append("TetrisGrid " + getWidth() + "x" + getHeight() + "\n");
		sbuilder.append(" ");
		for (int i = 0; i < getWidth(); i++) {
			sbuilder.append(i);
		}
		sbuilder.append("\n");
		for (int y = 0; y < getHeight(); y++) {
			sbuilder.append(y);
			for (int x = 0; x < getWidth(); x++) {
				sbuilder.append(getBlockAt(x, y).isBlocked() ? "X" : " ");
			}
			sbuilder.append("\n");
		}
		return sbuilder.toString();
	}

	/**
	 * Checks if is free.
	 * 
	 * @param tile
	 *            the tile
	 * @return true, if is free
	 */
	private boolean isFree(final TetrisTile tile) {
		return isFree(tile.getXPosition(), tile.getYPosition(), tile.getCurrentRelativePositions());
	}

	/**
	 * Checks if is ghost free.
	 * 
	 * @param xPosition
	 *            the x position
	 * @param yPosition
	 *            the y position
	 * @param relativePositions
	 *            the relative positions
	 * @return true, if is ghost free
	 */
	private boolean isGhostFree(final int xPosition, final int yPosition,
			final Point[] relativePositions) {
		for (final Point p : relativePositions) {
			if (checkBounds(xPosition + p.getXPosition(), yPosition + p.getYPosition())) {
				final TetrisBlock block =
						getBlockAt(xPosition + p.getXPosition(), yPosition + p.getYPosition());
				if (!(!block.isBlocked() || block.isTransparent() || block.isGhost())) {
					return false;
				}
			} else {
				return false;
			}
		}
		return true;
	}
}
